﻿//-----------------------------------------------------------------------------------------------------------
// WBFSSync Project by Omega Frost 
// http://wbfssync.codeplex.com/
//
// WBFSSync is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using WBFSe3.IO;
using System.Runtime.InteropServices;

namespace WBFSe3.Streams
{
    //-------------------------------------------------------------------------------------------------------
    //
    //-------------------------------------------------------------------------------------------------------
    public class WbfsMultiFileStream : WbfsStream
    {
        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        WbfsFileStream[] streams;
        WbfsFileStream activeStream;
        int activeIdx;
        long[] layout;
        long position;
        long length;


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override string Name
        {
            get { return this.streams[0].Name; }
        }

        public WbfsStream[] Streams
        {
            get { return this.streams; }
        }

        public override bool CanRead
        {
            get { return this.streams[0].CanRead; }
        }

        public override bool CanSeek
        {
            get { return this.streams[0].CanSeek; }
        }

        public override bool CanWrite
        {
            get { return this.streams[0].CanWrite; }
        }

        public override long Length
        {
            get { return this.length; }
        }

        public override long Position
        {
            get { return this.position; }
            set { Seek(value, System.IO.SeekOrigin.Begin); }
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public WbfsMultiFileStream(WbfsPath files, long[] layout, int sectorSize, FileMode mode)
            : base(sectorSize)
        {
            this.length = 0;
            this.streams = new WbfsFileStream[files.Count];
            this.layout = new long[files.Count + 1];
            this.layout[0] = 0;
            this.position = -1;

            for (int i = 0; i < this.streams.Length; i++)
            {
                this.streams[i] = new WbfsFileStream(files[i], this.sectorSize, mode);

                if (layout == null)
                {
                    this.length += this.streams[i].Length;
                    this.layout[i + 1] = this.layout[i] +
                        this.streams[i].Length;
                }
                else
                {
                    this.length += layout[i];
                    this.layout[i + 1] = layout[i];
                }
            }

            Seek(0, SeekOrigin.Begin);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public WbfsMultiFileStream(WbfsPath files, long[] layout, int sectorSize, FileMode mode, FileAccess access)
            : base(sectorSize)
        {
            this.length = 0;
            this.streams = new WbfsFileStream[files.Count];
            this.layout = new long[files.Count + 1];
            this.layout[0] = 0;
            this.position = -1;

            for (int i = 0; i < this.streams.Length; i++)
            {
                this.streams[i] = new WbfsFileStream(files[i], this.sectorSize, mode, access);

                if (layout == null)
                {
                    this.length += this.streams[i].Length;
                    this.layout[i + 1] = this.streams[i].Length;
                }
                else
                {
                    this.streams[i].SetLength(layout[i]);
                    this.length += layout[i];
                    this.layout[i + 1] = layout[i];
                }
            }

            Seek(0, SeekOrigin.Begin);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public WbfsMultiFileStream(WbfsPath files, long[] layout, int sectorSize, FileMode mode, FileAccess access, FileShare share)
            : base(sectorSize)
        {
            this.length = 0;
            this.streams = new WbfsFileStream[files.Count];
            this.layout = new long[files.Count + 1];
            this.layout[0] = 0;
            this.position = -1;

            for (int i = 0; i < this.streams.Length; i++)
            {
                this.streams[i] = new WbfsFileStream(files[i], this.sectorSize, mode, access, share);

                if (layout == null)
                {
                    this.length += this.streams[i].Length;
                    this.layout[i + 1] = this.streams[i].Length;
                }
                else
                {
                    this.streams[i].SetLength(layout[i]);
                    this.length += layout[i];
                    this.layout[i + 1] = layout[i];
                }
            }

            Seek(0, SeekOrigin.Begin);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void Flush()
        {
            foreach (Stream s in this.streams)
                s.Flush();
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void Close()
        {
            foreach (Stream s in this.streams)
                s.Close();

            base.Close();
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void SetLength(long value)
        {
            // Nah...
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override long Seek(long offset, SeekOrigin origin)
        {
            switch (origin)
            {
                case SeekOrigin.Current: offset += this.position; break;
                case SeekOrigin.End: offset = this.length - offset; break;
            }

            if (this.position != offset)
            {
                int i = 0;
                while (offset > this.layout[i + 1]) i++;

                this.activeIdx = i;
                this.activeStream = this.streams[this.activeIdx];
                this.activeStream.Position = offset - layout[i];
                this.position = offset;
            }

            return this.position;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override int Read(byte[] buffer, int offset, int count)
        {
            int read = 0;
            while (count > 0)
            {
                long max = this.layout[this.activeIdx + 1] - this.activeStream.Position;
                int toread = (int)(count > max ? max : count);

                this.activeStream.Read(buffer, offset + read, toread);
                read += toread;
                count -= toread;

                if ((max == toread) && (++this.activeIdx < this.streams.Length))
                    this.activeStream = this.streams[this.activeIdx];
            }

            this.position += read;
            return read;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void Write(byte[] buffer, int offset, int count)
        {
            int written = 0;
            while (count > 0)
            {
                long max = this.layout[this.activeIdx + 1] - this.activeStream.Position;
                int towrite = (int)(count > max ? max : count);

                this.activeStream.Write(buffer, offset + written, towrite);
                written += towrite;
                count -= towrite;

                if ((max == towrite) && (++this.activeIdx < this.streams.Length))
                    this.activeStream = this.streams[this.activeIdx];
            }

            this.position += written;
        }

        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override int ReadSector(int sector, IntPtr array, int offset)
        {
            Byte[] buffer = new Byte[this.sectorSize];

            Seek((long)this.SectorSize * sector, SeekOrigin.Begin);
            Read(buffer, 0, this.SectorSize);
            Marshal.Copy(buffer, 0, IntPtr.Add(array, offset), this.sectorSize);
            return this.SectorSize;
        }

        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override int WriteSector(int sector, IntPtr array, int offset)
        {
            Byte[] buffer = new Byte[this.sectorSize];
            Marshal.Copy(array, buffer, offset, this.sectorSize);

            Seek((long)this.SectorSize * sector, SeekOrigin.Begin);
            Write(buffer, 0, this.SectorSize);
            return this.SectorSize;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void WriteEmptyBlock(int count)
        {
            int written = 0;
            Byte[] zeroes = new Byte[4096];
            
            while (written < count)
            {
                int towrite = Math.Min(4096, count - written);
                Write(zeroes, 0, towrite);
                written += towrite;
            }
        }
    }
}
